from rtl_token import *
from exception import RTLScanException

class RTLScanner():

    KEYWORDS : dict[str, TokenType] = {
        'module' : TokenType.MODULE,
        'endmodule' : TokenType.ENDMODULE,
        'input' : TokenType.INPUT,
        'output' : TokenType.OUTPUT,
        'wire' : TokenType.WIRE,
        'reg' : TokenType.REG,
    }

    def __init__(self, rtl_file_path : str) -> None:
        with open(rtl_file_path, 'r') as file:
            self.__context = file.read()
        self.__tokens : list[Token] = []
        self.__start_index = 0
        self.__current_index = 0
        self.__line = 0
    
    def scan(self) -> list[Token]:
        while self.__at_end() == False:
            self.__start_index = self.__current_index
            self.__scan_token()
        
        self.__tokens.append(Token(TokenType.EOF))
        return self.__tokens

    def __at_end(self) -> bool:
        return self.__current_index >= len(self.__context)

    def __advance_ch(self) -> str:
        ch = self.__context[self.__current_index]
        self.__current_index += 1
        return ch
    
    def __advance(self) -> None:
        self.__current_index += 1

    def __peek_ch(self) -> str:
        if self.__at_end():
            return '\0'
        return self.__context[self.__current_index]

    def __match(self, ch : str) -> bool:
        if self.__at_end() == True:
            return False
        if self.__context[self.__current_index] != ch:
            return False
        self.__current_index += 1
        return True
    
    def __scan_number(self) -> int:
        while True:
            ch = self.__peek_ch()
            if ch.isdigit() == True:
                self.__advance()
            else:
                break
        
        # __context[self.__start_index, self.__current_index) is digit string
        return int(self.__context[self.__start_index : self.__current_index])

    def __scan_identifier(self) -> str:
        while True:
            ch = self.__peek_ch()
            if ch.isalnum() == True or ch == '_':
                self.__advance()
            else:
                break

        # __context[self.__start_index, self.__current_index) is identifier string
        return self.__context[self.__start_index : self.__current_index]

    def __append_token(self, token_type : TokenType, literal: object = None):
        self.__tokens.append(Token(token_type, literal))

    def __scan_token(self):
        ch = self.__advance_ch()
        match ch:
            case '(': self.__append_token(TokenType.LEFT_PAREN)
            case ')': self.__append_token(TokenType.RIGHT_PAREN)
            case '[': self.__append_token(TokenType.LEFT_BRACKET)
            case ']': self.__append_token(TokenType.RIGHT_BRACKET)
            case ',': self.__append_token(TokenType.COMMA)
            case ':': self.__append_token(TokenType.COLON)
            case '.': self.__append_token(TokenType.DOT)
            case ';': self.__append_token(TokenType.SEMICOLON)
            case '/':
                if self.__match('/'):
                    while self.__peek_ch() != '\n' and self.__at_end() == False:
                        self.__advance()
                else:
                    raise RTLScanException(self.__line, '/' + self.__advance_ch())
            case ' ' | '\r' | '\t': pass
            case '\n': self.__line += 1
            # IDENTIFIER or INTEGER
            case _:
                if ch.isdigit():
                    self.__append_token(TokenType.INTEGER, self.__scan_number())
                else:
                    idtifier_name = self.__scan_identifier()
                    if idtifier_name in RTLScanner.KEYWORDS:
                        self.__append_token(RTLScanner.KEYWORDS[idtifier_name])
                    else:
                        self.__append_token(TokenType.IDENTIFIER, idtifier_name)
        